package com.iqik.base.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Scanner;

/**
 * NIO缺点：
 *      可靠性不强，开发难度大，工作量大
 *      NIO的BUG，Epoll Bug会导致selector空轮询，造成cpu开销100%
 *      API复杂，需要掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
 */

public class NIOServer extends Thread {

    public static void main(String[] args) {
        new NIOServer(8200).start();
    }

    // 多路复用器
    private Selector selector;

    // 读写缓冲区
    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);

    public NIOServer(int port) {
        init(port);
    }

    private void init(int port) {
        try {
            System.out.println("server init...");
            // 开启多路复用
            this.selector = Selector.open();
            // 开启服务通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            // 绑定端口
            serverSocketChannel.bind(new InetSocketAddress(port));
            /**
             * SelectionKey.OP_ACCEPT 接收连接继续事件，表示服务器监听到客户连接，服务器可以接收这个连接
             * SelectionKey.OP_CONNECT 连接就绪事件，表示客户与服务的连接已经建立成功
             * SelectionKey.OP_READ 读就绪事件，表示通道中已经有了可读的数据，可以执行读操作
             * SelectionKey.OP_WRITE    写就绪事件，表示已经可以向通道，写数据
             */
            // 注册标记服务器连接状态为ACCEPT
            serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
            System.out.println("have been started");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                // 至少有一个通道被选中，执行此方法
                this.selector.select();
                // 获取选中的通道编号集合
                Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
                // 便利通道的key
                while (keys.hasNext()) {
                    SelectionKey key = keys.next();
                    // 当前key需要从集合中移除，如果不移除，下次循环会执对应的逻辑，造成业务混乱
                    keys.remove();
                    if (key.isValid()) {
                        try {
                            // 判断是否可以连接
                            if (key.isAcceptable()) {
                                accept(key);
                            }
                        } catch (CancelledKeyException e) {
                            // 异常断开连接
                            key.cancel();
                        }

                        try {
                            // 判断是否可读
                            if (key.isReadable()) {
                                read(key);
                            }
                        } catch (CancelledKeyException e) {
                            // 异常断开连接
                            key.cancel();
                        }

                        try {
                            // 判断是否可写
                            if (key.isWritable()) {
                                write(key);
                            }
                        } catch (CancelledKeyException e) {
                            // 异常断开连接
                            key.cancel();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void accept(SelectionKey key) {

        try {
            // 当前通道在init方法中注册了selector中的ServerSocketChannel
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            // 阻塞方法，客户端发起请求返回
            SocketChannel channel = serverSocketChannel.accept();
            // serverSocketChannel设置为非阻塞
            channel.configureBlocking(false);
            // 设置对应客户端的通道标记，标记为可读
            channel.register(this.selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void read(SelectionKey key) {
        try {
            // 清空缓存
            this.readBuffer.clear();
            // 获取当前通道对象
            SocketChannel channel = (SocketChannel) key.channel();
            // 将通道的数据读到缓存中
            int readLen = channel.read(this.readBuffer);
            // 通道中没有数据
            if (readLen == -1) {
                key.channel().close();
                return;
            }
            // buffer中有游标，游标不会重制，需要我们用flip重制，否则读取不一致
            this.readBuffer.flip();
            // 创建有效字节长度数组
            byte[] bytes = new byte[readBuffer.remaining()];
            // 将数据保存到字节数组中
            readBuffer.get(bytes);
            System.out.println(String.format("收到客户端 %s: %s", channel.getRemoteAddress(), new String(bytes, "UTF-8")));
            // 通道标记为可写操作
            channel.register(this.selector, SelectionKey.OP_WRITE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void write(SelectionKey key) {
        try {
            // 清空缓存
            this.writeBuffer.clear();
            // 获取当前通道对象
            SocketChannel channel = (SocketChannel) key.channel();
            // 录入数据
            Scanner scanner = new Scanner(System.in);
            // 发送response数据
            System.out.println("send data to client。。。");
            String line = scanner.nextLine();
            // 写数据到buffer
            writeBuffer.put(line.getBytes("utf-8"));
            // 重制游标
            writeBuffer.flip();
            // 写数据
            channel.write(writeBuffer);
            channel.register(this.selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
